package org.springframework.boot.loader;

import java.io.ByteArrayInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.net.URLClassLoader;
import java.net.URLConnection;
import java.net.URLStreamHandler;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.jar.JarEntry;
import java.util.jar.JarInputStream;

import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;

import org.springframework.boot.loader.archive.Archive;
import org.springframework.boot.loader.archive.Archive.EntryFilter;

/**
 * 
 * @author 北京八维博大科技 薛建新
 * @date 2022年11月9日 10:41:55
 * @Copyright 北京八维博大科技
 */
public class JarLauncher extends ExecutableArchiveLauncher {

	static final EntryFilter NESTED_ARCHIVE_ENTRY_FILTER = (entry) -> {
		if (entry.isDirectory()) {
			return entry.getName().equals("BOOT-INF/classes/");
		}
		return entry.getName().startsWith("BOOT-INF/lib/");
		// || entry.getName().startsWith("BOOT-INF/lib1/") ;
	};

	public JarLauncher() {
	}

	protected JarLauncher(Archive archive) {
		super(archive);
	}

	@Override
	protected boolean isPostProcessingClassPathArchives() {
		return false;
	}

	@Override
	protected boolean isNestedArchive(Archive.Entry entry) {
		return NESTED_ARCHIVE_ENTRY_FILTER.matches(entry);
	}

	@Override
	protected String getArchiveEntryPathPrefix() {
		return "BOOT-INF/";
	}

	public static void main(String[] args) throws Exception {
		new JarLauncher().launch(args);

//		File root = new File("D:/work20220906/java/myopen/springboot2/demo20220728/target/demo20220728-0.0.1-SNAPSHOT.jar");
//		File root = new File("D:/a.jar");

//		new JarLauncher(new JarFileArchive(root)).launch(args);
	}

	@Override
	protected ClassLoader createClassLoader(URL[] urls) throws Exception {
		System.out.println("^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^");
		// test
		// System.out.println(new
		// String(this.getClass().getResourceAsStream("/BOOT-INF/lib1/touch.txt").readAllBytes()));
//		return createClassLoader1(urls);
//		return createClassLoader2(urls);
		return createClassLoader3(urls);
	}

	@Deprecated
	private ClassLoader createClassLoader1(URL[] urls) throws Exception {

		List<String> jarFiles = new ArrayList<>();
		jarFiles.add("demo20220728test-1.0.0.jar");

		final String jarPath = "/demo20220728test-1.0.0.jar!/";
//		File jarFilePath=new File("D:/a/BOOT-INF/lib1/demo20220728test-1.0.0.jar");
//		URL jarURL= new URL("file:/D:/a/BOOT-INF/lib1/demo20220728test-1.0.0.jar");
//		final String jarPath= jarURL.toURI().toString();//"file:D:/a/BOOT-INF/lib1/demo20220728test-1.0.0.jar";

		System.out.println("【" + jarPath + "】");

		byte[] key = "1234567890abcdef".getBytes();
		final Map<String, byte[]> map = new HashMap<>();
		try {
			// 解密文件
			byte[] decrypted = null;

			for (String jarFile : jarFiles) {

				// 读取加密文件
//				var fileData = this.getClass().getResourceAsStream("/BOOT-INF/lib1/" + jarFile).readAllBytes();
				var fileData = Files.readAllBytes(Path.of("D:/a/BOOT-INF/lib1/demo20220728test-1.0.0.jar"));
				// 解密文件
				decrypted = decrypt(key, fileData);

				try (JarInputStream is = new JarInputStream(new ByteArrayInputStream(decrypted))) {
					for (;;) {
						JarEntry nextEntry = is.getNextJarEntry();
						if (nextEntry == null)
							break;
						final int eSize = (int) nextEntry.getSize();
						byte[] data = new byte[eSize > 0 ? eSize : 1024];
						int real = 0;
						for (int r = is.read(data); r > 0; r = is.read(data, real, data.length - real))
							if (data.length == (real += r))
								data = Arrays.copyOf(data, data.length * 2);
						if (real != data.length)
							data = Arrays.copyOf(data, real);
						// url.getFile() 没有 buffer
						String name = jarPath + nextEntry.getName();
						System.out.println("Jar entry:" + name);
						map.put(name, data);

					}
				}

			}

			final byte[] jarContent = decrypted;

			// final URL u = new URL("buffer", null, -1,
			// "file:/D:/a.jar!/BOOT-INF/lib/demo20220728test-1.0.0.jar!/", new
			// URLStreamHandler() {
			final URL u = new URL("buffer", null, -1, jarPath, new URLStreamHandler() {
				protected URLConnection openConnection(URL url) throws IOException {
					if (url.getFile().startsWith(jarPath + "com/xue/test")) {
						System.out.println("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!>>>>" + url.toString());

						final byte[] data = map.get(url.getFile());
						System.out.println("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!>>>>" + url.getFile());
						if (data == null)
							throw new FileNotFoundException(url.getFile());

						return new URLConnection(url) {
							public void connect() throws IOException {
							}

							@Override
							public InputStream getInputStream() throws IOException {
								return new ByteArrayInputStream(data);
							}
						};
					} else if (url.getFile().equals(jarPath)) {
//						System.out.println("****************"); 获取了整个jar 内容
						return new URLConnection(url) {
							public void connect() throws IOException {
							}

							@Override
							public InputStream getInputStream() throws IOException {
								return new ByteArrayInputStream(jarContent);
							}
						};
					} else {
						throw new FileNotFoundException(url.getFile());
					}

				}
			});

			URL[] newUrls = new URL[urls.length + 1];
			for (int i = 0; i < urls.length; i++) {
				System.out.println(i + "==>>" + urls[i]);
				newUrls[i] = urls[i];
			}
			newUrls[urls.length] = u;
//			newUrls[urls.length] = new URL("jar:file:/D:/a.jar!/BOOT-INF/lib1/demo20220728test-1.0.0.jar!/");
			System.out.println((urls.length) + "==>>" + newUrls[urls.length]);

			URLClassLoader c1 = new URLClassLoader("bufferURLClassLoader", new URL[] { u },
					getClass().getClassLoader());
//			 c2 的parent 是c1,c2加载不了的委托给c1
//			URLClassLoader c2 = new LaunchedURLClassLoader(isExploded(), getArchive(), urls, c1);
			URLClassLoader c2 = new LaunchedURLClassLoader(isExploded(), getArchive(), newUrls, c1);

			return c2;
		} catch (Exception e) {
			e.printStackTrace();
		}
		return null;

	}

	private ClassLoader createClassLoader3(URL[] urls) {
		// 参数需要包的名称以 . 分割输入，看来可以直接加密jar包
		class MyLaunchedURLClassLoader extends LaunchedURLClassLoader {
			public MyLaunchedURLClassLoader(boolean exploded, Archive rootArchive, URL[] urls, ClassLoader parent) {
				super(exploded, rootArchive, urls, parent);
			}

			protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
				if (name.indexOf("com.xue.test") >= 0) {
					System.out.println("---------------->" + name);
					try {
						byte[] b = getResourceAsStream(name.replace(".", "/").concat(".class")).readAllBytes();
						return defineClass(name, b, 0, b.length);
					} catch (IOException e) {
						e.printStackTrace();
					}

				}
				return super.loadClass(name, resolve);
			}

			// 这个重写基本没有用处
			public URL findResource(final String name) {// 这个不必要重写
				// findResource---------------->com/xue/test/Hello.class
				if (name.indexOf("com/xue/test/") >= 0) {
					System.out.println("findResource---------------->" + name);
				}
				return super.findResource(name);

			}

			public InputStream getResourceAsStream(String name) {
				if (name.indexOf("com/xue/test/") >= 0 && name.endsWith(".class")) {
					// getResourceAsStream---------------->com/xue/test/Hello.class
					System.out.println("getResourceAsStream---------------->" + name);
					try {
						// 这里可以判断魔数后解密，如果是确定魔数开始解密，否则直接返回0xCAFEBABE
						return new ByteArrayInputStream(
								decrypt("1234567890abcdef".getBytes(), super.getResourceAsStream(name).readAllBytes()));
					} catch (IOException e) {
						e.printStackTrace();
					}

				}
				return super.getResourceAsStream(name);
			}

		}

		ClassLoader c2 = new MyLaunchedURLClassLoader(isExploded(), getArchive(), urls, getClass().getClassLoader());
		return c2;
	}

	@Deprecated
	private ClassLoader createClassLoader2(URL[] urls) {
		List<String> jarFiles = new ArrayList<>();
		jarFiles.add("demo20220728test-1.0.0.jar");
		ClassLoader c1 = new MemoryJarClassLoader(jarFiles, getClass().getClassLoader());
		URLClassLoader c2 = new LaunchedURLClassLoader(isExploded(), getArchive(), urls, c1);

//		URLClassLoader c1 = new LaunchedURLClassLoader(isExploded(), getArchive(), urls, getClass().getClassLoader() );
//		ClassLoader c2 = new MemoryJarClassLoader(jarFiles, c1);

		return c2;
	}

	// 解密:
	private static byte[] decrypt(byte[] key, byte[] input) {
		try {
			// 创建密码对象，需要传入算法/工作模式/填充模式
			Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");

			// 根据key的字节内容，"恢复"秘钥对象
			SecretKey keySpec = new SecretKeySpec(key, "AES");

			// 初始化秘钥:设置解密模式DECRYPT_MODE
			cipher.init(Cipher.DECRYPT_MODE, keySpec);

			// 根据原始内容(字节),进行解密
			return cipher.doFinal(input);
		} catch (Exception e) {
			e.printStackTrace();
		}
		return null;
	}

	private static final class MemoryJarClassLoader extends ClassLoader {
		final Map<String, byte[]> map = new HashMap<>();

		public MemoryJarClassLoader(List<String> jarFiles, ClassLoader parent) {
			super("memoryJarClassLoader", parent);
			// 128位密钥 = 16 bytes Key:
			byte[] key = "1234567890abcdef".getBytes();
			try {
				for (String jarName : jarFiles) {
					// 读取加密文件
					var fileData = this.getClass().getResourceAsStream("/BOOT-INF/lib1/" + jarName).readAllBytes();
					// 解密文件
					var decrypted = decrypt(key, fileData);

					try (JarInputStream is = new JarInputStream(new ByteArrayInputStream(decrypted))) {
						for (;;) {
							JarEntry nextEntry = is.getNextJarEntry();
							if (nextEntry == null)
								break;
							final int eSize = (int) nextEntry.getSize();
							byte[] data = new byte[eSize > 0 ? eSize : 1024];
							int real = 0;
							for (int r = is.read(data); r > 0; r = is.read(data, real, data.length - real))
								if (data.length == (real += r))
									data = Arrays.copyOf(data, data.length * 2);
							if (real != data.length)
								data = Arrays.copyOf(data, real);

							String name = "/" + nextEntry.getName();
							System.out.println("Jar entry:" + name);

							map.put(name, data);

							map.put("/" + nextEntry.getName(), data);
						}
					}

				}
			} catch (Exception e) {
				e.printStackTrace();
			}
		}

		@Override
		public Class<?> findClass(String name) throws ClassNotFoundException {

			if (name.startsWith("com.xue.test")) {// 包名称
				System.out.println("======>" + name);
				String classSubfix = ".class";
				String key = name.replace('.', '/').concat(classSubfix);
//				String key = "/" + name.replaceAll("\\.", "/") + classSubfix;
				System.out.println("======>" + key);
				byte[] b = map.get(key);
				if (b == null) {
					throw new ClassNotFoundException(name);
				} else {
					System.out.println(name + " found!!!");
					return defineClass(name, b, 0, b.length);
				}

			}

			throw new ClassNotFoundException(name);

		}
		/*
		 * //RestartClassLoader
		 * 
		 * @Override public URL findResource(String name) { final ClassLoaderFile file =
		 * this.updatedFiles.getFile(name); if (file == null) { return
		 * super.findResource(name); } if (file.getKind() == Kind.DELETED) { return
		 * null; } return AccessController.doPrivileged((PrivilegedAction<URL>) () ->
		 * createFileUrl(name, file)); }
		 * 
		 * private URL createFileUrl(String name, ClassLoaderFile file) { try { return
		 * new URL("reloaded", null, -1, "/" + name, new
		 * ClassLoaderFileURLStreamHandler(file)); } catch (MalformedURLException ex) {
		 * throw new IllegalStateException(ex); } }
		 */

	}

}
